﻿namespace Filters.Demo
{
    using System;
    using System.Collections.Generic;
    using System.Drawing;
    using System.Globalization;
    using System.IO;
    using System.Linq;
    using System.Windows.Forms;
    using AForge.Math;
    using Filters.Demo.CustomFilters;
    using Filters.Demo.DataAccess;
    using Filters.Demo.MetricSpaces;

    public partial class ContourForm : Form
    {
        private const int FirmSize = 800;
        private const int UIBuffer = 40;
        private const int MaxControlCount = 2;
        private const NumberStyles NumberStyle = NumberStyles.Float | NumberStyles.Integer | NumberStyles.Number;

        private readonly Dictionary<Point, EdgePoint> contourPoints;
        private readonly Image currentImage;
        private readonly string originalImageFilePath;
        private readonly IImageCatalogRepository repository;
        private readonly IMetricSpacesHelper metricSpacesHelper;

        public ContourForm(IDictionary<Point, EdgePoint> contourPoints, Image currentImage, string originalImageFilePath, IImageCatalogRepository repository, IMetricSpacesHelper metricSpacesHelper)
        {
            this.contourPoints = new Dictionary<Point, EdgePoint>(contourPoints);
            this.repository = repository;
            this.metricSpacesHelper = metricSpacesHelper;
            this.currentImage = currentImage;
            this.originalImageFilePath = originalImageFilePath;

            this.InitializeComponent();

            this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.DoubleBuffer | ControlStyles.ResizeRedraw, true);
            this.mainSplitContainer.SplitterDistance = this.Width - 225;
        }

        // Paint image
        private void SplitContainerPanelPaint(object sender, PaintEventArgs e)
        {
            if ((this.contourPoints != null) && (this.contourPoints.Keys != null) && (this.contourPoints.Keys.Count > 0))
            {
                var g = e.Graphics;
                var pen = new Pen(Color.Gray);
                var pen2 = new Pen(Color.Red);
                var br = new SolidBrush(Color.Gray);

                g.DrawPolygon(pen, this.contourPoints.Keys.ToArray<Point>());
                g.FillPolygon(br, this.contourPoints.Keys.ToArray<Point>());

                foreach (var pair in this.contourPoints)
                {
                    g.DrawRectangle(pen2, pair.Key.X, pair.Key.Y, 1, 1);
                }
            }
        }

        private void ViewSignatureBtnClick(object sender, System.EventArgs e)
        {
            var points = string.Empty;
            var firm = this.GenerateFourierDescriptors();
            foreach (var pt in firm)
            {
                points += string.IsNullOrEmpty(points) ? "{" : ";";
                points += "(" + pt.Re + "," + pt.Im + ")";
            }

            points += "}";
            MessageBox.Show(points, "Fourier Descriptors", MessageBoxButtons.OK, MessageBoxIcon.Information);
        }

        private void SaveDescriptorsBtnClick(object sender, System.EventArgs e)
        {
            var imageModel = new ImageModel
            {
                ImageName = Path.GetFileName(this.originalImageFilePath),
                ImageBinarized = this.currentImage,
                ImageOriginal = Image.FromFile(this.originalImageFilePath),
                ImageSignature = this.GenerateFourierDescriptors()
            };

            var message = "The image and its signature were successfully saved in the database.";
            var messageBoxIcon = MessageBoxIcon.Information;
            try
            {
                this.metricSpacesHelper.SetPivotInformation(imageModel);
                this.repository.CreateImageModel(imageModel);
            }
            catch (Exception exception)
            {
                message = exception.Message;
                messageBoxIcon = MessageBoxIcon.Error;                
            }
            finally
            {
                MessageBox.Show(message, "Save Image Results", MessageBoxButtons.OK, messageBoxIcon);
            }
        }

        private void FindSimilarImages()
        {
            double distance = 0d;
            if (!double.TryParse(this.maxDistanceTxtBox.Text.Replace(',', '.'), NumberStyle, CultureInfo.InvariantCulture, out distance))
            {
                MessageBox.Show(
                    string.Format(CultureInfo.CurrentUICulture, "{0} is not a valid distance.", this.maxDistanceTxtBox.Text),
                    "Validation Error",
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Error);

                return;
            }

            var similarImages = this.metricSpacesHelper.GetSimilarImages(this.GenerateFourierDescriptors(), distance);
            if ((similarImages == null) || (similarImages.Count <= 0))
            {
                MessageBox.Show(
                    string.Format(CultureInfo.CurrentUICulture, "No images were found at distance {0}. Please try using another value.", distance),
                    "No Similiar Images Found",
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Information);

                return;
            }

            var orderedControls = similarImages
                .Select(im => new ImageModelControl { ImageModel = im })
                .OrderBy(c => c.ImageModel.DistanceToTarget)
                .ToArray();
            var count = 0;
            foreach (var control in orderedControls)
            {
                control.Location = new Point(0, count * control.Size.Height);
                count++;
            }

            var similiarImagesForm = new SimilarImagesForm();
            if ((count <= MaxControlCount) && (count > 0))
            {
                similiarImagesForm.Height = (orderedControls[0].Size.Height * count) + UIBuffer + 180;
            }
            else
            {
                similiarImagesForm.Height = (orderedControls[0].Size.Height * MaxControlCount) + UIBuffer + 180;
            }

            similiarImagesForm.LoadResult(orderedControls, this.originalImageFilePath, this.currentImage, distance);
            similiarImagesForm.ShowDialog();
        }

        private Complex[] GenerateFourierDescriptors()
        {
            var pointsToTransform = new Complex[this.contourPoints.Count];

            var u = 0;
            foreach (var pair in this.contourPoints)
            {
                // first select points from 0 to N/2-1
                if (u < (this.contourPoints.Count / 2))
                {
                    pointsToTransform[u] = new Complex(pair.Key.X, pair.Key.Y);
                    u++;
                }
            }

            u = 1;
            foreach (var pair in this.contourPoints.Reverse())
            {
                // next select points from N-1 to N-N/2
                if (u <= (this.contourPoints.Count / 2))
                {
                    pointsToTransform[this.contourPoints.Count - u] = new Complex(pair.Key.X, pair.Key.Y);
                    u++;
                }
            }

            FourierTransform.DFT(pointsToTransform, FourierTransform.Direction.Forward);

            var finalFirm = new Complex[FirmSize];

            if (pointsToTransform.Count() < FirmSize)
            {
                var sizeDifference = FirmSize - pointsToTransform.Count();
                for (var i = 0; i < (this.contourPoints.Count / 2); i++)
                {
                    finalFirm[i] = new Complex(pointsToTransform[i].Re, pointsToTransform[i].Im);
                }

                var zeroStartPos = this.contourPoints.Count / 2;
                var zeroEndPos = (this.contourPoints.Count / 2) + sizeDifference;
                for (var i = zeroStartPos; i < zeroEndPos; i++)
                {
                    finalFirm[i] = Complex.Zero;
                }

                var finalFirmPos = zeroEndPos;
                for (var i = this.contourPoints.Count / 2; i > 0; i--)
                {
                    finalFirm[finalFirmPos] = new Complex(pointsToTransform[pointsToTransform.Count() - i].Re, pointsToTransform[pointsToTransform.Count() - i].Im);
                    finalFirmPos++;
                }
            }
            else if (pointsToTransform.Count() > FirmSize)
            {
                for (var i = 0; i < (FirmSize / 2); i++)
                {
                    finalFirm[i] = new Complex(pointsToTransform[i].Re, pointsToTransform[i].Im);
                }

                var finalFirmPos = FirmSize / 2;
                for (var i = FirmSize / 2; i > 0; i--)
                {
                    finalFirm[finalFirmPos] = new Complex(pointsToTransform[pointsToTransform.Count() - i].Re, pointsToTransform[pointsToTransform.Count() - i].Im);
                    finalFirmPos++;
                }
            }
            else
            {
                finalFirm = pointsToTransform;
            }

            return finalFirm;
        }

        private void FindSimilarBtnClick(object sender, EventArgs e)
        {
            this.FindSimilarImages();
        }

        private void MaxDistanceTxtBoxPreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
        {
            if (e.KeyData == Keys.Enter)
            {
                this.FindSimilarImages();
            }
        }
    }
}
